package org.xiaoyu.feign.core.annotation;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.StringUtils;
import org.xiaoyu.feign.core.FeignFactoryBean;
import org.xiaoyu.feign.core.FeignZookeeper;

import feign.Client;

/**
 * feign服务的扫描
 * @author zhangsaizz
 *
 */
public class ClassPathFeignServiceScanner extends ClassPathBeanDefinitionScanner {
	

	private Logger log = LoggerFactory.getLogger(ClassPathFeignServiceScanner.class);
	
	private Class<? extends Annotation> annotationClass;

	private Class<?> markerInterface;
	
	private FeignFactoryBean<?> feignFactoryBean = new FeignFactoryBean<Object>();


	public Class<?> getMarkerInterface() {
		return markerInterface;
	}

	public void setMarkerInterface(Class<?> markerInterface) {
		this.markerInterface = markerInterface;
	}

	public Class<? extends Annotation> getAnnotationClass() {
		return annotationClass;
	}

	public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
		this.annotationClass = annotationClass;
	}

	public ClassPathFeignServiceScanner(BeanDefinitionRegistry registry) {
		super(registry);
	}

	private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) throws InstantiationException, IllegalAccessException {
		GenericBeanDefinition definition;
		for (BeanDefinitionHolder holder : beanDefinitions) {
			definition = (GenericBeanDefinition) holder.getBeanDefinition();
			definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
			try {
				Class<?> o = Class.forName(definition.getBeanClassName());
				FeignConsumer feignService = o.getAnnotation(FeignConsumer.class);
				Config config = o.getAnnotation(Config.class);
				if(feignService.urls().length == 0 && StringUtils.isEmpty(feignService.name())) {
					throw new IllegalArgumentException("serviceName and serviceUrl must have a presence!!");
				}
				definition.getConstructorArgumentValues().addGenericArgumentValue(feignService.urls());
				definition.getPropertyValues().add("name", feignService.name());
				if(!StringUtils.isEmpty(feignService.name())) {
					FeignZookeeper.serviceAndUrl.put(feignService.name(), new ArrayList<>());
					if(!getRegistry().containsBeanDefinition(FeignConsumerRegister.FEIGN_ZOOKEEPER)) {
						String ip = getEnvironment().getProperty("feign.zookeeper.ip");
						if(StringUtils.isEmpty(ip)) {
							log.warn("[feign.zookeeper.ip] is default addresss : [127.0.0.1:2181]");
							ip = "127.0.0.1:2181";
						}
						BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignZookeeper.class);
						ZooKeeper zooKeeper = null;
						try {
							 zooKeeper = new ZooKeeper(ip, 1000, new Watcher() {	
								@Override
								public void process(WatchedEvent event) {
								}
							});
						} catch (IOException e) {
							e.printStackTrace();
						}
						builder.addConstructorArgValue(zooKeeper);
						getRegistry().registerBeanDefinition(FeignConsumerRegister.FEIGN_ZOOKEEPER, builder.getBeanDefinition());	
					}
				}
				if(config != null) {
					definition.getPropertyValues().add("decoder", config.decoder().newInstance());
					definition.getPropertyValues().add("encoder", config.encoder().newInstance());
					definition.getPropertyValues().add("errorDecoder", config.errorDecoder().newInstance());
					definition.getPropertyValues().add("logger", config.logger().newInstance());
					definition.getPropertyValues().add("contract", config.contract().newInstance());
					definition.getPropertyValues().add("retryer", config.retryer().newInstance());
					definition.getPropertyValues().add("queryMapEncoder", config.queryMapEncoder().newInstance());
					definition.getPropertyValues().add("decode404", config.decode404());
					definition.getPropertyValues().add("options", config.options().newInstance());
					if(config.client().equals(Client.Default.class)) {
						definition.getPropertyValues().add("client", new Client.Default(null, null));
					}else {
						definition.getPropertyValues().add("client", config.client().newInstance());
					}
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}			
			definition.setBeanClass(this.feignFactoryBean.getClass());
			definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);	

		}
	}

	public void registerFilters() {
		boolean acceptAllInterfaces = true;

		if (this.annotationClass != null) {
			addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
			acceptAllInterfaces = false;
		}

		if (this.markerInterface != null) {
			addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
				@Override
				protected boolean matchClassName(String className) {
					return false;
				}
			});
			acceptAllInterfaces = false;
		}

		if (acceptAllInterfaces) {
			addIncludeFilter(new TypeFilter() {
				@Override
				public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
						throws IOException {
					return true;
				}
			});
		}
	}

	@Override
	public Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

		if (beanDefinitions.isEmpty()) {
			logger.warn("No feign service was found in '" + Arrays.toString(basePackages)
					+ "' package. Please check your configuration.");
		} else {
			try {
				processBeanDefinitions(beanDefinitions);
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}

		return beanDefinitions;
	}

	@Override
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		Set<String> ans = beanDefinition.getMetadata().getAnnotationTypes();
		String anName = FeignConsumer.class.getTypeName();
		boolean f = false;
		for (String a : ans) {
			if(a.equals(anName)) {
				f = true;
				break;
			}
		}
		if(f && beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent()) {
			return true;
		}
		return false;
	}


	@Override
	protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
		if (super.checkCandidate(beanName, beanDefinition)) {
			return true;
		} else {
			logger.warn("Skipping FeignFactoryBean with name '" + beanName + "' and '" + beanDefinition.getBeanClassName()
							+ "' feignServiceInterface" + ". Bean already defined with the same name!");
			return false;
		}
	}
	
	public FeignFactoryBean<?> getFeignFactoryBean() {
		return feignFactoryBean;
	}

	public void setFeignFactoryBean(FeignFactoryBean<?> feignFactoryBean) {
		this.feignFactoryBean = feignFactoryBean;
	}


}
